<?php
/**
 * vim: tabstop=4
 * 
 * @license		http://www.gnu.org/licenses/gpl.html GPL Version 3
 * @author		Ian Moore <imooreyahoo@gmail.com>
 * @copyright	Copyright (c) 2011 Ian Moore
 * @version		$Id: dhcpd.inc 12 2011-11-07 18:52:10Z imooreyahoo@gmail.com $
 *
 * This file is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this file. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

require_once("openmediavault/object.inc");
require_once("openmediavault/error.inc");
require_once("openmediavault/util.inc");
require_once("openmediavault/rpc.inc");
require_once("openmediavault/notify.inc");

class DHCPDRpc extends OMVRpc {
	
	const xpathRoot = '//services/dhcpd';
	
	public function __construct() {

		$this->methodSchemata = array(
		
			"set" => array('{
				"type":"object",
				"properties":{
					"enable":{"type":"boolean"},
					"domain-name":{"type":"string"},
					"name-servers":{"type":"string","optional":true},
					"bootfile":{"type":"string","optional":true},
					"gateway":{"type":"string"},
					"default-lease-time":{"type":"string"},
					"name-servers":{"type":"string","optional":true},
					"wins-servers":{"type":"string","optional":true},
					"network":{"type":"string"},
					"first-ip":{"type":"string"},
					"last-ip":{"type":"string"}
				}
			}'),
			"getReservations" => array(
				'{"type":"integer"}', // start
				'{"type":"integer"}', // count
				'{'.$GLOBALS['OMV_JSONSCHEMA_SORTFIELD'].'}', // sortField
				'{'.$GLOBALS['OMV_JSONSCHEMA_SORTDIR'].'}' // sortDir
			),
            "getLeaseList" => array(
                '{"type":"integer"}', // start
                '{"type":"integer"}', // count
                '{'.$GLOBALS['OMV_JSONSCHEMA_SORTFIELD'].'}', // sortField
                '{'.$GLOBALS['OMV_JSONSCHEMA_SORTDIR'].'}' // sortDir
            ),
			"setReservation" => array('{
				"type":"object",
				"properties":{
					"uuid":{'.$GLOBALS['OMV_JSONSCHEMA_UUID_UNDEFINED'].'},
					"ip":{"type":"string","format":"regex","pattern":"\/^(([0-9]{1,3}\\\.){3}[0-9]{1,3}|)$\/i","optional":true},
					"name":{"type":"string"},
					"bootfile":{"type":"string","optional":true},
					"bootconfig":{"type":"string"},
					"mac":{"type":"string","format":"regex","pattern":"\/^([a-f0-9]{2}:){5}[a-f0-9]{2}$\/i"},
					"exlease":{"type":"string","optional":true}
				}
			}'),
			"getReservation" => array(
				'{'.$GLOBALS['OMV_JSONSCHEMA_UUID'].'}'
			),
			"removeReservation" => array(
				'{'.$GLOBALS['OMV_JSONSCHEMA_UUID'].'}'
			)

		);
	}

	/**
	 * Safe config getting amd setting
	 */
	public function __call($name, $args) {
		
		// Configuration methods
		if(substr($name,0,6) == 'config') {
			
			// Correct method name
			$name = substr($name,6);
			$name[0] = strtolower($name[0]);
			
			global $xmlConfig;
			$object = call_user_func_array(array($xmlConfig,$name),$args);
			switch($name) {
				case 'delete':
					if($object === false)
						throw new OMVException(OMVErrorMsg::E_CONFIG_OBJECT_NOT_FOUND, $args[0]);
					break;
				case 'save':
					if($object === false)
						throw new OMVException(OMVErrorMsg::E_CONFIG_SAVE_FAILED, $xmlConfig->getError());
					break;
				case 'set':
				case 'replace':
					if($object === false)
						throw new OMVException(OMVErrorMsg::E_CONFIG_SET_OBJECT_FAILED);
					break;
				default:
					if(is_null($object))
						throw new OMVException(OMVErrorMsg::E_CONFIG_GET_OBJECT_FAILED, $args[0]);
			}
			
			return $object;			
			
			
		}
		throw new Exception("Method ".__CLASS__."::".$name." does not exist.");
	}
	
	/**
	 * Verify that the current user is an admin, and validate method args
	 */
	function _validate($mname='',$args=array()) {
		
		// Check permissions
		$this->validateSession();
		if (!$this->hasRole(OMV_ROLE_ADMINISTRATOR)) {
			throw new OMVException(OMVErrorMsg::E_RPC_SERVICE_INVALID_PERMISSION);
		}
		$this->commitSession();
			
		// Check incoming data
		if($mname)
			$this->validateParams($mname, $args);
	}
	
	/**
	 * Get all configuration data for service.
	 * @return array configuration data
	 */
	function get() {
		
		// Validation
		$this->_validate();
		
		//Get configuration object
		$object = $this->configGet(self::xpathRoot);
		
		// Modify result data
		$object['enable'] = boolval($object['enable']);
		return $object;
	}

	/**
	 * Set configuration data for service.
	 * @param $object array configuration data
	 */
	function set($object) {

		// Validation
		$this->_validate(__METHOD__,func_get_args());
		
		
		// Prepare configuration data
		$object["enable"] = array_boolval($object, 'enable');
		list($object['subnet'], $object['netmask']) = explode(' / ', $object['network']);
		
		# Check that gateway, first ip, and last ip are in range
		if(long2ip(ip2long($object['gateway']) & ip2long($object['netmask'])) != $object['subnet']) {
			throw new Exception("Gateway address {$object['gateway']} is not in network.");
		}

        if(long2ip(ip2long($object['first-ip']) & ip2long($object['netmask'])) != $object['subnet']) {
            throw new Exception("First IP address {$object['first-ip']} is not in network.");
        }

        if(long2ip(ip2long($object['last-ip']) & ip2long($object['netmask'])) != $object['subnet']) {
            throw new Exception("Last IP address {$object['last-ip']} is not in network.");
        }

		try {
        	$econf = $this->get();
			$exRes = $econf["reservations"];
		} catch (Exception $e) {
		}

		if(empty($exRes)) $exRes = array();
		$object['reservations'] = $exRes;

		// Set configuration object
		$this->configReplace(self::xpathRoot, $object);

		$this->configSave();
		
		// Notify general configuration changes
		$dispatcher = &OMVNotifyDispatcher::getInstance();
		$dispatcher->notify(OMV_NOTIFY_MODIFY,
			"org.openmediavault.services.dhcpd", $object);

	}

	/**
	 * Remove reservation from config
	 * @param string $uuid configuration uuid of reservation
	 */
	public function removeReservation($uuid) {

		// Validation
		$this->_validate(__METHOD__,func_get_args());

		// Get configuration object for notification
		$object = $this->configGet(self::xpathRoot."/reservations/reservation[uuid='{$uuid}']");
		
		// Delete configuration object
		$this->configDelete(self::xpathRoot."/reservations/reservation[uuid='{$uuid}']");
		
		// Save configuration
		$this->configSave();
		
		// Notify configuration changes
		$dispatcher = &OMVNotifyDispatcher::getInstance();
		$dispatcher->notify(OMV_NOTIFY_DELETE,
			"org.openmediavault.services.dhcpd.reservations.reservation", $object);
	}

	/**
	 * Get list of reservations
	 * @param $start integer start point in paging list
	 * @param $count integer number of objects to return in paged list
	 * @param $sortField string field to sort on
	 * @param $sortDir integer sort direction
	 * @return array list of reservations
	 */
	public function getReservations($start, $count, $sortField, $sortDir) {

		// Validation
		$this->_validate(__METHOD__,func_get_args());
		
		$objects = $this->configGetList(self::xpathRoot."/reservations/reservation");

		// Filter result
		return $this->applyFilter($objects, $start, $count, $sortField, $sortDir);
		
	}

	/**
	 * Save reservation configuration data
	 * @param $data array reservation configuration
	 */
	public function setReservation($data) {

		// Validation
		$this->_validate(__METHOD__,func_get_args());
		
		// Prepare configuration data
		$object = $data;
		if($object['exlease']) unset($object['exlease']);
		$object['uuid'] = ($data['uuid'] == $GLOBALS['OMV_UUID_UNDEFINED']) ? OMVUtil::uuid() : $data['uuid'];

		// Set configuration data
		if ($data['uuid'] == $GLOBALS['OMV_UUID_UNDEFINED']) {

			// Check uniqueness
			if (TRUE === $this->configExists(self::xpathRoot."/reservations/reservation[name='{$object['name']}']")) {
				throw new OMVException(OMVErrorMsg::E_CONFIG_OBJECT_UNIQUENESS);
			}

            // Check uniqueness
            if (TRUE === $this->configExists(self::xpathRoot."/reservations/reservation[ip='{$object['ip']}']")) {
                throw new OMVException(OMVErrorMsg::E_CONFIG_OBJECT_UNIQUENESS);
            }

            // Check uniqueness
            if (TRUE === $this->configExists(self::xpathRoot."/reservations/reservation[mac='{$object['mac']}']")) {
                throw new OMVException(OMVErrorMsg::E_CONFIG_OBJECT_UNIQUENESS);
            }

			// Append object to configuration
			$this->configSet(self::xpathRoot."/reservations",array("reservation" => $object));
			
		} else {

			// Update existing configuration object
			$this->configReplace(self::xpathRoot."/reservations/reservation[uuid='{$data['uuid']}']", $object);


		}
		
		// Save configuration
		$this->configSave();
		
		// Notify configuration changes
		$dispatcher = &OMVNotifyDispatcher::getInstance();
		$dispatcher->notify(($data['uuid'] == $GLOBALS['OMV_UUID_UNDEFINED']) ?
			OMV_NOTIFY_CREATE : OMV_NOTIFY_MODIFY,
			"org.openmediavault.services.dhcpd.reservations.reservation", $object);


	}

	/**
	 * Get a single reservation's configuration
	 * @param $uuid string configuration uuid of reservation
	 * @return array reservation configuration data
	 */
	public function getReservation($uuid) {

		// Validation
		$this->_validate(__METHOD__,func_get_args());
		
		// get existing reservation
		return $this->configGet(self::xpathRoot."/reservations/reservation[uuid='{$uuid}']");

	}

	/**
	 * Get a list of networks for this machine.
	 * @return array networks
	 */
	public function getNetworks() {

		$this->_validate();

		$ints = $this->configGetList("//system/network/interfaces/iface");
		$nets = array();

		foreach($ints as $i) {

			if(!($i['netmask'] && $i['address'])) continue;

			$netid = long2ip(ip2long($i['address']) & ip2long($i['netmask'])) . " / {$i['netmask']}";

			$nets[$netid] = $netid;
		}


		$networks = array();
		foreach($nets as $n) $networks[] = array('netid'=>$n);
		return $networks;		
	}


	/**
	 * Return a list of active leases
	 * @param $start integer start point in paging list
	 * @param $count integer number of objects to return in paged list
	 * @param $sortField string field to sort on
	 * @param $sortDir integer sort direction
	 * @return array list of leases
	 */
    public function getLeaseList($start, $count, $sortField, $sortDir) {

        // Validation
        $this->_validate(__METHOD__,func_get_args());

        $objects = $this->getLeases(true);


        // Filter result
        return $this->applyFilter($objects, $start, $count, $sortField, $sortDir);

    }

    /**
     * 
     * Returns a list of active leases used for combo box population
     * when adding a reservation
     * @return array list of leases
     */
	public function getLeases() {

		$this->_validate();

		$fp = @fopen("/var/lib/dhcp/dhcpd.leases", "ro");
		$leases = array();

		if($fp === false) return $leases;

		$exp=$name=$mac=$ip = '';

		while(!feof($fp)) {

			$line = trim(fgets($fp));
		
			if($line == "}") {

				if($name) $disp = "{$name} ({$ip})";
				else $disp = $ip;

				if($exp)
					$leases[] = array('ip'=>$ip,'mac'=>$mac,'name'=>$name,'disp'=>$disp,'exp'=>$exp);
				
				$exp=$ip=$mac=$name='';

			} elseif(strpos($line,'lease ') === 0) {
				$ip = substr($line, 6, -2);
			} elseif(strpos($line, 'hardware ethernet') === 0) {
				list($null,$null,$mac) = explode(' ', substr($line,0,-1), 3);
			} elseif(strpos($line, 'client-hostname') === 0) {
                list($null,$name) = explode('"', $line, 3);
			} elseif(strpos($line, 'ends ') === 0) {

				$exp = explode(' ', substr($line,0,-1));
				list($y,$m,$d) = explode('/',$exp[2]);
				list($h,$mi,$s) = explode(':',$exp[3]);

				$exp = gmmktime($h,$mi,$s,$m,$d,$y) - time();
				if($exp < 0) {
					$exp = null;
					continue;
				}

				$days = floor($exp / 86400);
				$exp-=($days*86400);

				$hours = floor($exp / 3600);
				$exp-=($hours*3600);

				$mins = floor($exp/60);
			
				$exp = array();	
				if($days > 0) $exp[] = "{$days} day(s)";
				if($hours > 0) $exp[] = "{$hours} hour(s)";
				if($mins > 0) $exp[] = "{$mins} minute(s)";

				$exp = implode(", ", $exp);

			}

			
		}

		return $leases;

	}

}
